#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <semaphore.h>
#include <unistd.h>

#include <curses.h>

#define QTD_PRODUTOS 1000
#define QTD_PRODUTORES 3
#define QTD_CONSUMIDORES 5
#define TAM_FILA 50


int estado_semaforo [10][4];

pthread_mutex_t interface_grafica; 

int n_produzidos, n_fila, prox_consumir, prox_produzir, n_consumidos;
int fila[TAM_FILA];
int value;

//controla o acesso as variaveis de producao e consumo
sem_t mutexp, mutexc; 

//começa com 0. Caso 0 não é possível consumir.
sem_t n_produtos;

//começa com TAM_FILA. Caso 0 não é possível produzir.
sem_t espaco_fila;
	
void atualiza_valor_semaforos() {
	int v1, v2, v3, v4;

	sem_getvalue(&n_produtos, &v1);
	pthread_mutex_lock(&interface_grafica);
	move(10, 1);
	printw("n_produtos: sincronização, [%3d]", v1);
	pthread_mutex_unlock(&interface_grafica);

	sem_getvalue(&espaco_fila, &v2);
	pthread_mutex_lock(&interface_grafica);
	move(11, 1);
	printw("espaco_fila: sincronização, [%3d]", v2);
	pthread_mutex_unlock(&interface_grafica);

	sem_getvalue(&mutexp, &v3);
	pthread_mutex_lock(&interface_grafica);
	move(12, 1);
	printw("mutexp: exclusão mutua, [%3d]", v3);
	pthread_mutex_unlock(&interface_grafica);

	sem_getvalue(&mutexc, &v4);
	pthread_mutex_lock(&interface_grafica);
	move(13, 1);
	printw("mutexc: exclusão mutua, [%3d]", v4);
	pthread_mutex_unlock(&interface_grafica);
}

void atualiza_fila_produtos(int n_thread, int valor, char situacao, int i) {
	pthread_mutex_lock(&interface_grafica);
	move(15 + i%25, 35*(i/25) + 4);
	printw("[%d] %c%d", valor, situacao, n_thread);
	refresh();
	pthread_mutex_unlock(&interface_grafica);
}

void atualiza_estado_thread(int situacao, int linha) {
	pthread_mutex_lock(&interface_grafica);
	move(linha, 4);
	switch(situacao) {
		case 0: { printw("executando  "); break; }
		case 1: { printw("dormindo    "); break; }
		case 2: { printw("fim         "); break; }
	}	
	refresh();
	pthread_mutex_unlock(&interface_grafica);
}

void *run_produtor(void *arg) {
	long long int num = (long long int) arg;

	while (1) {

		atualiza_valor_semaforos();
		sem_wait(&espaco_fila);
		atualiza_valor_semaforos();

		atualiza_estado_thread(1, num + 1 + QTD_CONSUMIDORES);
		atualiza_valor_semaforos();
		sem_wait(&mutexp);
		atualiza_valor_semaforos();
		atualiza_estado_thread(0, num + 1 + QTD_CONSUMIDORES);
		if (QTD_PRODUTOS - n_produzidos <= num) {
			//printf("\n----------> P%d: Bye bye", num);
			atualiza_estado_thread(2, num + 1 + QTD_CONSUMIDORES);
			atualiza_valor_semaforos();
			sem_post(&mutexp);
			sem_post(&n_produtos);
			atualiza_valor_semaforos();
			pthread_exit(NULL);
		}
		fila[prox_produzir] = n_produzidos;
		atualiza_fila_produtos(num, n_produzidos, 'P', prox_produzir);
		//printf("\nP%d I%d", num, n_produzidos);
		prox_produzir = (prox_produzir + 1) % TAM_FILA;
		n_produzidos++;
		atualiza_valor_semaforos();
		sem_post(&mutexp);
		atualiza_valor_semaforos();

		atualiza_valor_semaforos();
		sem_post(&n_produtos);
		atualiza_valor_semaforos();
	}
}

void *run_consumidor(void *arg) {
	long long int num = (long long int) arg;

	while (1) {
		atualiza_valor_semaforos();
		sem_wait(&n_produtos);
		atualiza_valor_semaforos();

		usleep(rand() % 10000);

		atualiza_estado_thread(1, num);
		atualiza_valor_semaforos();
		sem_wait(&mutexc);
		atualiza_valor_semaforos();
		atualiza_estado_thread(0, num);
		atualiza_fila_produtos(num, n_consumidos, 'C', prox_consumir);
		//printf("\nC%d I%d", num, fila[prox_consumir]);

		prox_consumir = (prox_consumir + 1) % TAM_FILA;	
		n_consumidos++;
		atualiza_valor_semaforos();
		sem_post(&mutexc);
		atualiza_valor_semaforos();

		if (QTD_PRODUTOS - n_consumidos <= num) {
			//printf("\n----------> C%d: Bye bye", num);
			atualiza_estado_thread(2, num);
			atualiza_valor_semaforos();
			sem_post(&mutexc);
			sem_post(&espaco_fila);
			atualiza_valor_semaforos();
			pthread_exit(NULL);
		}

		atualiza_valor_semaforos();
		sem_post(&espaco_fila);
		atualiza_valor_semaforos();
	}
}

int main(void) {
	int qtd_threads, i, offset;
	pthread_t *threads;
	
	pthread_mutex_init(&interface_grafica, NULL);

	initscr();
	start_color();

	init_pair(1, COLOR_WHITE, COLOR_BLUE); 
	init_pair(2, COLOR_BLUE, COLOR_WHITE); 
	init_pair(3, COLOR_RED, COLOR_WHITE);  

	bkgd(COLOR_PAIR(1));  
	attron(COLOR_PAIR(3)); 

	refresh(); 

	n_produzidos = 0;
	n_consumidos = 0;
	prox_produzir = 0;
	prox_consumir = 0;
	
	sem_init(&n_produtos, 0, 0);  
	sem_init(&espaco_fila, 0, TAM_FILA);  
	
	sem_init(&mutexp, 0, 1);  
	sem_init(&mutexc, 0, 1);  
	atualiza_valor_semaforos(); 

	qtd_threads = QTD_CONSUMIDORES + QTD_PRODUTORES;

	threads = (pthread_t*) malloc (qtd_threads * sizeof(pthread_t));
	
	offset = 15;
	
	pthread_mutex_lock(&interface_grafica);
	for(i = 0; i < TAM_FILA; i++) {
		move(offset + i%25, 35*(i/25));
		printw("%2d", i);
	}

	//Cria as threads de consumidores
	for (i = 0; i < QTD_CONSUMIDORES; i++) {
		move(i, 1);  
		printw("C%d", i); 
		pthread_create(&threads[i], NULL, run_consumidor, (void *) i);
	}

	//Cria as threads de produtores
	for (i = 0; i < QTD_PRODUTORES; i++) {
		move(i + 1 + QTD_CONSUMIDORES, 1);  
		printw("P%d", i); 
		pthread_create(&threads[i+QTD_CONSUMIDORES], NULL, run_produtor, (void *) i);
	}
	refresh();
	pthread_mutex_unlock(&interface_grafica);

	for (i = 0; i < qtd_threads; i++)
		pthread_join(threads[i], NULL);		

	//printf("\n\n### QTD_PRODUTOS=%d n_produzidos=%d n_consumidos=%d\n", QTD_PRODUTOS, n_produzidos, n_consumidos);

	getch();
	endwin();

	return 0;
}

